/**
 * 数组
 * 数组的方法
 *  forEach: 遍历，默认改变原数组，不返回新数组
 *  map：遍历，必须用reture返回，返回新数组
 */
// 创建一个空数组
var arr = new Array()
console.log(arr,arr[0],arr.length)        //[] undefined 0
// 创建一个长度为5的数组
var arr = new Array(5)
console.log(arr,arr[0],arr.length)        //[ <5 empty items> ] undefined 5
// 创建一个有值的数组
var arr = new Array(1,2,3)
console.log(arr,arr[0],arr.length)        //[ 1, 2, 3 ] 1 3

var arr = []              //常用简写，创建一个空数组
var arr = [1,2,3]         //创建一个有元素1，2，3的数组
arr.length = 5            //修改arr的长度为5，，，注意，如果原来arr的长度大于5，就把5之后的都不要了。

// ---------------------------------------------------------------------------------------------------------------------
/**
 * 数组的增删改查
 *  push：往后面加，改变原数组,返回数组增加后的长度
 *  pop: 取最后一个元素，改变原数组，返回被取出的元素
 *  unshift: 往前面加，改变原数组，返回数组增加后的长度
 *  shift： 取第一个元素，改变原数组，返回被取出的元素
 *  splice：删除任意位置元素，或者在任意位置添加元素，改变原数组,,返回被删除元素数组
 */
var arr = [0]
// console.log(arr.push(),arr)      //1   [ 0 ]
// console.log(arr.push(1),arr)    //2   [ 0, 1 ]
// console.log(arr.pop(),arr)    //1    [ 0 ]
console.log(arr.unshift(-1),arr)  //2    [ -1, 0 ]
// console.log(arr.unshift(-1,-2),arr)  //3    [ -1, -2, 0 ]
console.log(arr.shift(),arr)    //-1   [ 0 ]
// 总结：pop和shift不接受传入参数，你传了不会影响结果，push和unshift可以传一个参数，或者多个参数，也可以不传参数，每个参数之间用逗号隔开

var arr1 = [0,1,2]
// var result = arr1.splice(0,1)       //第一个参数表示从0索引开始，第二个参数表示删除多少个
// console.log(result,arr1)            //[ 0 ]  [ 1, 2 ]
result = arr1.splice(0,1,'我是要被添加的第一个元素','我是要被添加的第二个元素','我是要....')    //从第三个参数开始，都是要被加入到数组的元素
console.log(result,arr1)              //[ 0 ]   [ '我是要被添加的第一个元素', '我是要被添加的第二个元素', '我是要....', 1, 2 ]
// 总结：splice(index,deleteNum,addEle,addEle,...)   第一个参数是从这个索引开始，第二个参数是要删除多少个，后面的参数依次为要添加的元素


// ---------------------------------------------------------------------------------------------------------------------
/**
 * 数组自身操作和数组之间的操作
 *  toString：这个可以不用管，
 *  join：把数组元素连接起来变成字符串，返回拼成的字符串，不改变原数组         ==================不改变原数组==================
 *  concat：合并多个数组，返回新数组，不改变原数组                          ==================不改变原数组==================
 *  slice：截取数组,返回新数组，不改变原数组                              ==================不改变原数组==================
 *  sort: 数组排序，返回排序后的数组，改变原数组
 *  reverse: 反转数组元素，返回反转后的数组，改变原数组
 */
var arr = [0,1]
console.log(arr.toString(),arr)         //0,1 [ 0, 1 ]
console.log(arr.join(),arr)             //0,1 [ 0, 1 ]
console.log(arr.join(''),arr)           //01  [ 0, 1 ]
// join接收一个参数作为每个元素之间拼接的符号，这个符号可以是任意，如果不传参，默认用逗号拼接

var arr = [0,1],arr1=[2,3],arr2='test'
console.log(arr.concat(arr1,arr2))      //[ 0, 1, 2, 3, 'test' ]
// concat可以接收多个参数，会依次把这些元素都放进数组里

var arr = ['a','b','c','d']
console.log(arr.slice(1,2),arr)         //[ 'b' ]        [ 'a', 'b', 'c', 'd' ]
console.log(arr.slice(2),arr)         //[ 'c', 'd' ]     [ 'a', 'b', 'c', 'd' ]
console.log(arr.slice(),arr)         //[ 'a', 'b', 'c', 'd' ]    [ 'a', 'b', 'c', 'd' ]
// slice(startIndex，endIndex)  截取数组部分内容，第一个参数表示开始索引(不传默认0)，第二个参数表示结束索引(不传默认截取到最后)
// 截取的时候不包含开始索引元素，但包含结束索引的元素，简称左开右闭，数学表示为  (startIndex,endIndex]   例: (1,2]

var arr = [3,1,4,2]
// console.log(arr.sort(),arr)         //[ 1, 2, 3, 4 ] [ 1, 2, 3, 4 ]
var result = arr.sort(function(a,b){
  // return a - b                  //升序，，，默认
  // return b - a                  //降序
  return 0                          //0，代表不排序，就是不做任何操作
})
console.log(result,arr)               //[ 4, 3, 2, 1 ] [ 4, 3, 2, 1 ]
// sort(cb)可以接受一个函数作为参数，这个函数会有2个参数，第一个代表当前元素，第二个代表下一个元素，
// 如果return 第一个参数-第二个参数，就代表升序排序，也是默认
// 如果return 第二参数-第一个参数，就是代表降序排序，

var arr = [3,1,4,2]
console.log(arr.reverse(),arr)    //[ 2, 4, 1, 3 ]  [ 2, 4, 1, 3 ]

/**
 * 数组高级遍历方法
 *  forEach：遍历数组，，可对数组的每一个元素做操作，改变原数组
 *  map： 遍历数组，返回新数组
 *  filter: 过滤数组，返回新数组
 *  find：  查找数组中第一个符合要求的元素，返回这个被找到的元素，如果找不到，返回undefined
 *  findIndex: 查找数组中第一个符合要求的元素，返回这个被找到的元素的索引，如果找不到，返回-1
 *  reduce：统计数组，返回统计后的元素
 *  reduceRight：从后边开始统计数组：返回统计后的元素
 *  every：检查所有的元素是否都符合要求，返回布尔 true/false
 *  some：检查是否有元素符合要求，返回布尔        true/false
 *  indexOf：查找某个元素在数组中首次出现的位置，返回索引值，查不到返回-1
 *  lastIndexOf：同上功能，查找元素在数组中最后一次出现的位置，返回索引值，查不到返回-1
 */
var arr = [1,2,3,4,5]
var result = arr.forEach(function(item,index,arr){
  //item指元素，index指元素索引，arr指原数组
})
console.log(result,arr)       //undefined [ 1, 2, 3, 4, 5 ]
var result = arr.forEach(function(item,index,arr){
  //item指元素，index指元素索引，arr指原数组
  console.log(item)             // 1 2 3 4 5
  return arr[index] = item+1        //强行return也没用，forEach的返回值永远是undefined，它改变的是原数组
})
console.log(result,arr)       //undefined [ 2, 3, 4, 5, 6 ]
// ****************************map
var arr = ['a','b','c']
var result = arr.map(function(item,index,arr){
                              //你不加return，map也会默认return
})
console.log(result,arr)       //[ undefined, undefined, undefined ]  [ 'a', 'b', 'c' ]
var result = arr.map(function(item,index,arr){
  return item+item                //对每一个元素做自加操作，这里必须retrun自己的内容，不然会默认return [undefined, undefined, undefined ]
})
console.log(result,arr)       //[ 'aa', 'bb', 'cc' ] [ 'a', 'b', 'c' ]
// ****************************filter
var arr = [1,2,3,4,5]
var result = arr.filter(function(item,index,arr){
  //如果不写return，默认返回一个空数组
  return item > 3             //返回比3大的元素
})
console.log(result,arr)               //[ 4, 5 ] [ 1, 2, 3, 4, 5 ]
// **********************************every
var arr = [1,2,3,4,5]
var result = arr.every(function(item,index,arr){
  //如果不写return，默认返回false
  return item > 3             //所有元素是否都大于3
})
console.log(result,arr)           //false [ 1, 2, 3, 4, 5 ]
// **********************************some
var arr = [1,2,3,4,5]
var result = arr.some(function(item,index,arr){
  //如果不写return，默认返回false
  return item > 3            //所有元素是否都大于3
})
console.log(result,arr)           //true [ 1, 2, 3, 4, 5 ]
// **********************************indexOf
var arr = [1,2,3,4,5]
var result = arr.indexOf(2)       //查找2在数组中的索引
console.log(result,arr)         //1 [ 1, 2, 3, 4, 5 ]
// indexOf(item,startIndex)和lastIndexOf(item,startIndex)可以传2个参数，第二个表示要从哪个位置开始查找。

// **********************************find
var arr = [1,2,3,4,5]
var result = arr.find(function(item,index,arr){
  //如果不写任何代码默认返回undefined
  return item > 3                 //查找第一个比4大的元素，并且返回这个元素
})       
console.log(result,arr)         //4 [ 1, 2, 3, 4, 5 ]
// **********************************findIndex
var arr = [1,2,3,4,5]
var result = arr.findIndex(function(item,index,arr){
  //如果不写任何代码默认返回-1
  return item > 3                   //查找第一个比4大的元素，并且返回这个元素的索引
})       
console.log(result,arr)         //3 [ 1, 2, 3, 4, 5 ]         //这里的3是索引
// **********************************reduce
var arr = [1,2,3,4,5]
var result = arr.reduce(function(prepData,item,index,arr){
  //如果不写任何代码默认返回undefined
  return prepData + item                   //对数组元素进行叠加
},0)       
console.log(result,arr)         //15 [ 1, 2, 3, 4, 5 ]

// reduce(cb,prepData)接收2个参数，第一个回调函数，第二个初始值(可以不传),如果传了初始值,那么回调函数就变成了有4个参数，
// 第一个参数是这个初始值，第二个是每个元素，然后索引，原数组
// 每次遍历，回调函数的第一个参数都会变成return的值

// ********************************************************************reduceRight
var arr = [1,2,3,4,5]
var result = arr.reduceRight(function(prepData,item,index,arr){
  //如果不写任何代码默认返回undefined
  console.log(item)                         // 5 4 3 2 1
  return prepData + item                   //对数组元素进行叠加 
},0)       
console.log(result,arr)         //15 [ 1, 2, 3, 4, 5 ]          
//---------------------------------------------------------------------------------------------------------------------
/**
 * Date   用来表示一个时间
 * 实例方法
 *  getDate：返回当前时间“几月几日”的“几日”给你  
 */

var d =new Date();      // 创建一个当前时间对象
// var d = new Date("月/日/年 时：分：秒")      // 创建一个指定时间对象
console.log(d.getDate())      // 获取当前对象是几号 1-31
console.log(d.getDay())       // 获取当前对象是星期几 0表示周日 1表示周一 0-6
console.log(d.getMonth())     // 从Data对象中返回月份 0表示一月  1表示二月  0~11
console.log(d.getFullYear())    // 获取当前对象的年份
console.log(d.getHours())    // 返回Data对象的小时0~23
console.log(d.getMinutes() )  // 返回Data对象的分钟0~59
console.log(d.getSecconds())  // 返回Data对象的秒数 0~59
console.log(d.getMilliseconds())    // 返回Data对象的毫秒 0~999
/*获取当前对象的时间戳
时间戳
  指从1970年1月1日0分0时0秒到现在的时间所花费的毫秒数
  计算机底层的时间都是以时间戳的形式保存
*/
getTime()
Data.now()   //获取当前的时间戳

//--------------------------------------------------------------------------------------------------------------
/*
Object类
  Object 是所有类的父类，任何类都直接或间接的继承 Object 类。由于所有的类都继承在Object类，因此省略了extends Object关键字。
  Object类有12个成员方法，分别是clone():Object 、equals(Object):boolean、
  finalize():void、getClass() 、hashCode():int、notify():void、\notifyAll():void、
  toString():String、wait():void、wait(long):void、wait(long,int):void
  */

 //--------------------------------------------------------------------------------------------------------------
/*
Object 构造函数的方法
1 object.assign()  方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
*/
const target = { a: 1, b: 2 , c: 8};
const source = { b: 6, c: 9 };
const returnedTarget = Object.assign(target, source);
console.log(target);      // expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget);      // expected output: Object { a: 1, b: 4, c: 5 }

//--------------------------------------------------------------------------------------------------------------
/*
Object.create()方法创建一个新对象，使用现有的对象来提供新创建的对象的__proto__。 （请打开浏览器控制台以查看运行结果。）
JavaScript Demo: Object.create()
*/
const person = {
  isHuman: false,
  printIntroduction: function () {
  console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
  }
};
const me = Object.create(person);
me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();     // expected output: "My name is Matthew. Am I human? true"

//----------------------------------------------------------------------------------------------------------------

/*
Object.defineProperty() 方法会直接在一个对象上定义一个新属性，或者修改一个对象的现有属性， 并返回这个对象。
返回值节
    被传递给函数的对象。
在ES6中，由于 Symbol类型的特殊性，用Symbol类型的值来做对象的key与常规的定义或修改不同，
而Object.defineProperty 是定义key为Symbol的属性的方法之一。

该方法允许精确添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的，能够在属性枚举期间呈现出来
（for...in 或 Object.keys 方法）， 这些属性的值可以被改变，也可以被删除。这个方法允许修改默认的额外选项（或配置）。
默认情况下，使用 Object.defineProperty() 添加的属性值是不可修改的。

属性描述符节
对象里目前存在的属性描述符有两种主要形式：数据描述符和存取描述符。数据描述符是一个具有值的属性，该值可能是可写的，
也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一；不能同时是两者。
  configurable:
  当且仅当该属性的 configurable 为 true 时，该属性描述符才能够被改变，同时该属性也能从对应的对象上被删除。默认为 false。
  enumerable
  当且仅当该属性的enumerable为true时，该属性才能够出现在对象的枚举属性中。默认为 false。

数据描述符同时具有以下可选键值：
value
该属性对应的值。可以是任何有效的 JavaScript 值（数值，对象，函数等）。默认为 undefined。
writable
当且仅当该属性的writable为true时，value才能被赋值运算符改变。默认为 false

存取描述符同时具有以下可选键值：
get
一个给属性提供 getter 的方法，如果没有 getter 则为 undefined。当访问该属性时，该方法会被执行，方法执行时没有参数传入，但是会传入this对象（由于继承关系，这里的this并不一定是定义该属性的对象）。
默认为 undefined。
set
一个给属性提供 setter 的方法，如果没有 setter 则为 undefined。当属性值修改时，触发执行该方法。该方法将接受唯一参数，即该属性新的参数值。
默认为 undefined。

描述符可同时具有的键值
        configurable	enumerable	value	 writable	  get	 set
数据描述符	Yes	           Yes	     Yes	   Yes	     No	  No
存取描述符	Yes	            Yes	     No	      No	     Yes	Yes
一个描述符不具有value,writable,get 和 set 任意一个关键字，那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字，将会产生一个异常。
记住，这些选项不一定是自身属性，如果是继承来的也要考虑。为了确认保留这些默认值，你可能要在这之前冻结 Object.prototype，明确指定所有的选项，或者通过 Object.create(null)将__proto__属性指向null。
  */
Object.defineProperty(obj, prop, descriptor)      //要在其上定义属性的对象。要定义或修改的属性的名称。要定义或修改的属性的名称
// 使用 __proto__
var obj = {};
var descriptor = Object.create(null); // 没有继承的属性
// 默认没有 enumerable，没有 configurable，没有 writable
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);

// 显式
Object.defineProperty(obj, "key", {
  enumerable: false,
  configurable: false,
  writable: false,
  value: "static"
});

// 循环使用同一对象
function withValue(value) {
  var d = withValue.d || (
    withValue.d = {
      enumerable: false,
      writable: false,
      configurable: false,
      value: null
    }
  );
  d.value = value;
  return d;
}
// ... 并且 ...
Object.defineProperty(obj, "key", withValue("static"));

// 如果 freeze 可用, 防止代码添加或删除对象原型的属性
// （value, get, set, enumerable, writable, configurable）
(Object.freeze||Object)(Object.prototype);
/*
创建属性节
如果对象中不存在指定的属性，Object.defineProperty()就创建这个属性。当描述符中省略某些字段时，
这些字段将使用它们的默认值。拥有布尔值的字段的默认值都是false。value，get和set字段的默认值为undefined。
一个没有get/set/value/writable定义的属性被称为“通用的”，并被“键入”为一个数据描述符。
*/
var o = {}; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
  value : 37,
  writable : true,
  enumerable : true,
  configurable : true
});

// 对象o拥有了属性a，值为37

// 在对象中添加一个属性与存取描述符的示例
var bValue;
Object.defineProperty(o, "b", {
  get : function(){
    return bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
  enumerable : true,
  configurable : true
});

o.b = 38;
// 对象o拥有了属性b，值为38

// o.b的值现在总是与bValue相同，除非重新定义o.b

// 数据描述符和存取描述符不能混合使用
Object.defineProperty(o, "conflict", {
  value: 0x9f91102, 
  get: function() { 
    return 0xdeadbeef; 
  } 
});
// throws a TypeError: value appears only in data descriptors, get appears only in accessor descriptors
/*
当writable属性设置为false时，该属性被称为“不可写”。它不能被重新分配。
如果属性已经存在，Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。
如果旧描述符将其configurable 属性设置为false，则该属性被认为是“不可配置的”，并且没有属性可以被改变
（除了单向改变 writable 为 false）。当属性不可配置时，不能在数据和访问器属性类型之间切换。
当试图改变不可配置属性（除了value和writable 属性之外）的值时会抛出TypeError，除非当前值和新值相同。
*/
var o = {}; // Creates a new object

Object.defineProperty(o, 'a', {
  value: 37,
  writable: false
});

console.log(o.a); // logs 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)
console.log(o.a); // logs 37. The assignment didn't work.

// strict mode
(function() {
  'use strict';
  var o = {};
  Object.defineProperty(o, 'b', {
    value: 2,
    writable: false
  });
  o.b = 3; // throws TypeError: "b" is read-only
  return o.b; // returns 2 without the line above
}());

/*Enumerable 特性
 enumerable定义了对象的属性是否可以在 for...in 循环和 Object.keys() 中被枚举。
*/
var o = {};
Object.defineProperty(o, "a", { value : 1, enumerable:true });
Object.defineProperty(o, "b", { value : 2, enumerable:false });
Object.defineProperty(o, "c", { value : 3 }); // enumerable defaults to false
o.d = 4; // 如果使用直接赋值的方式创建对象的属性，则这个属性的enumerable为true

for (var i in o) {    
  console.log(i);  
}
// 打印 'a' 和 'd' (in undefined order)

Object.keys(o); // ["a", "d"]

o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false

/*Configurable 特性
configurable特性表示对象的属性是否可以被删除，以及除value和writable特性外的其他特性是否可以被修改。
*/
var o = {};
Object.defineProperty(o, "a", { get : function(){return 1;}, 
                                configurable : false } );

// throws a TypeError
Object.defineProperty(o, "a", {configurable : true}); 
// throws a TypeError
Object.defineProperty(o, "a", {enumerable : true}); 
// throws a TypeError (set was undefined previously) 
Object.defineProperty(o, "a", {set : function(){}}); 
// throws a TypeError (even though the new get does exactly the same thing) 
Object.defineProperty(o, "a", {get : function(){return 1;}});
// throws a TypeError
Object.defineProperty(o, "a", {value : 12});

console.log(o.a); // logs 1
delete o.a; // Nothing happens
console.log(o.a); // logs 1
/*
添加多个属性和默认值节
考虑特性被赋予的默认特性值非常重要，通常，使用点运算符和Object.defineProperty()为对象的属性赋值时，
数据描述符中的属性默认值是不同的，如下例所示
*/
var o = {};
o.a = 1;
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : true,
  configurable : true,
  enumerable : true
});

// 另一方面，
Object.defineProperty(o, "a", { value : 1 });
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : false,
  configurable : false,
  enumerable : false
});
/*一般的 Setters 和 Getters节
下面的例子展示了如何实现一个自存档对象。 当设置temperature 属性时，archive 数组会获取日志条目。
*/
function Archiver() {
  var temperature = null;
  var archive = [];

  Object.defineProperty(this, 'temperature', {
    get: function() {
      console.log('get!');
      return temperature;
    },
    set: function(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });

  this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

var pattern = {
  get: function () {
      return 'I alway return this string,whatever you have assigned';
  },
  set: function () {
      this.myname = 'this is my name string';
  }
};


function TestDefineSetAndGet() {
  Object.defineProperty(this, 'myproperty', pattern);
}


var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';

// 'I alway return this string,whatever you have assigned'
console.log(instance.myproperty);
// 'this is my name string'
console.log(instance.myname);继承属性

/*继承属性
如果访问者的属性是被继承的，它的 get 和set 方法会在子对象的属性被访问或者修改时被调用。
如果这些方法用一个变量存值，该值会被所有对象共享。
*/
function myclass() {
}

var value;
Object.defineProperty(myclass.prototype, "x", {
  get() {
    return value;
  },
  set(x) {
    value = x;
  }
});

var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1

// 这可以通过将值存储在另一个属性中解决。在 get 和 set 方法中，this 指向某个被访问和修改属性的对象。
function myclass() {
}
Object.defineProperty(myclass.prototype, "x", {
  get() {
    return this.stored_x;
  },
  set(x) {
    this.stored_x = x;
  }
});
var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined

// 不像访问者属性，值属性始终在对象自身上设置，而不是一个原型。然而，如果一个不可写的属性被继承，它仍然可以防止修改对象的属性。
function myclass() {
}
myclass.prototype.x = 1;
Object.defineProperty(myclass.prototype, "y", {
  writable: false,
  value: 1
});
var a = new myclass();
a.x = 2;
console.log(a.x); // 2
console.log(myclass.prototype.x); // 1
a.y = 2; // Ignored, throws in strict mode
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1

//----------------------------------------------------------------------------------------------------------------

/*Object.defineProperties本质上定义了obj 对象上props的可枚举属性相对应的所有属性。
bject.defineProperties(obj, props)
obj在其上定义或修改属性的对象。
props要定义其可枚举属性或修改的属性描述符的对象。对象中存在的属性描述符主要有两种：数据描述符和访问器描述符（更多详情，请参阅Object.defineProperty()）。描述符具有以下键：
例子节
*/
var obj = {};
Object.defineProperties(obj, {
  'property1': {
    value: true,
    writable: true
  },
  'property2': {
    value: 'Hello',
    writable: false
  }
  // etc. etc.
});
//----------------------------------------------------------------------------------------------------------------

/*Polyfill
假设一个原始的执行环境，所有的名称和属性都引用它们的初始值，Object.defineProperties几乎完全等同于（
注意isCallable中的注释）以下JavaScript中的重新实现：
*/
function defineProperties(obj, properties) {
  function convertToDescriptor(desc) {
    function hasProperty(obj, prop) {
      return Object.prototype.hasOwnProperty.call(obj, prop);
    }

    function isCallable(v) {
      // NB: modify as necessary if other values than functions are callable.
      return typeof v === 'function';
    }

    if (typeof desc !== 'object' || desc === null)
      throw new TypeError('bad desc');

    var d = {};

    if (hasProperty(desc, 'enumerable'))
      d.enumerable = !!desc.enumerable;
    if (hasProperty(desc, 'configurable'))
      d.configurable = !!desc.configurable;
    if (hasProperty(desc, 'value'))
      d.value = desc.value;
    if (hasProperty(desc, 'writable'))
      d.writable = !!desc.writable;
    if (hasProperty(desc, 'get')) {
      var g = desc.get;

      if (!isCallable(g) && typeof g !== 'undefined')
        throw new TypeError('bad get');
      d.get = g;
    }
    if (hasProperty(desc, 'set')) {
      var s = desc.set;
      if (!isCallable(s) && typeof s !== 'undefined')
        throw new TypeError('bad set');
      d.set = s;
    }

    if (('get' in d || 'set' in d) && ('value' in d || 'writable' in d))
      throw new TypeError('identity-confused descriptor');

    return d;
  }

  if (typeof obj !== 'object' || obj === null)
    throw new TypeError('bad obj');

  properties = Object(properties);

  var keys = Object.keys(properties);
  var descs = [];

  for (var i = 0; i < keys.length; i++)
    descs.push([keys[i], convertToDescriptor(properties[keys[i]])]);

  for (var i = 0; i < descs.length; i++)
    Object.defineProperty(obj, descs[i][0], descs[i][1]);

  return obj;
}
/*
  JavaScript Demo: Object.entries()
  Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组，其排列与使用 for...in 
  循环遍历该对象时返回的顺序一致（区别在于 for-in 循环也枚举原型链中的属性）。
*/
const object1 = {
  a: 'somestring',
  b: 42
};

for (let [key, value] of Object.entries(object1)) {
  console.log(`${key}: ${value}`);
}

// expected output:
// "a: somestring"
// "b: 42"
// order is not guaranteed
/*
 Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改；冻结了一个对象则不能向这个对象添加新的属性，
 不能删除已有属性，不能修改该对象已有属性的可枚举性、可配置性、可写性，以及不能修改已有属性的值。此外，
 冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象
*/
const obj = {
  prop: 42
};

Object.freeze(obj);

obj.prop = 33;
// Throws an error in strict mode

console.log(obj.prop);
// expected output: 42

//----------------------------------------------------------------------------------------------------------------
Object.getOwnPropertyDescriptor(obj, prop)
/*
Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。
（自有属性指的是直接赋予该对象的属性，不需要从原型链上进行查找的属性）
参数节
obj
需要查找的目标对象
prop
目标对象内属性名称
返回值节
如果指定的属性存在于对象上，则返回其属性描述符对象（property descriptor），否则返回 undefined。
*/

var o, d;

o = { get foo() { return 17; } };
d = Object.getOwnPropertyDescriptor(o, "foo");
console.log(d)
// d {
//   configurable: true,
//   enumerable: true,
//   get: /*the getter function*/,
//   set: undefined
// }

o = { bar: 42 };
d = Object.getOwnPropertyDescriptor(o, "bar");
// d {
//   configurable: true,
//   enumerable: true,
//   value: 42,
//   writable: true
// }

o = {};
Object.defineProperty(o, "baz", {
  value: 8675309,
  writable: false,
  enumerable: false
});
d = Object.getOwnPropertyDescriptor(o, "baz");
// d {
//   value: 8675309,
//   writable: false,
//   enumerable: false,
//   configurable: false
// }


/*
注意事项节
在 ES5 中，如果该方法的第一个参数不是对象（而是原始类型），那么就会产生出现 TypeError。
而在 ES2015，第一个的参数不是对象的话就会被强制转换为对象。
*/
Object.getOwnPropertyDescriptor('foo', 0);
// 类型错误: "foo" 不是一个对象  // ES5 code

Object.getOwnPropertyDescriptor('foo', 0);
// Object returned by ES2015 code: {
//   configurable: false,
//   enumerable: true,
//   value: "f",
//   writable: false
// }

/*
Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名
（包括不可枚举属性但不包括Symbol值作为名称的属性）组成的数组。
语法节
Object.getOwnPropertyNames(obj)
参数节
obj
一个对象，其自身的可枚举和不可枚举属性的名称被返回。
返回值节
在给定对象上找到的自身属性对应的字符串数组。
描述
Object.getOwnPropertyNames() 返回一个数组，该数组对元素是 obj自身拥有的枚举或不可枚举属性名称字符串。 
数组中枚举属性的顺序与通过 for...in 循环（或 Object.keys）迭代该对象属性时一致。数组中不可枚举属性的顺序未定义。
*/

var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]

// 类数组对象
var obj = { 0: "a", 1: "b", 2: "c"};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]

// 使用Array.forEach输出属性名和属性值
Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
  console.log(val + " -> " + obj[val]);
});
// 输出
// 0 -> a
// 1 -> b
// 2 -> c

//不可枚举属性
var my_obj = Object.create({}, {
  getFoo: {
    value: function() { return this.foo; },
    enumerable: false
  }
});
my_obj.foo = 1;

console.log(Object.getOwnPropertyNames(my_obj).sort()); // ["foo", "getFoo"]

var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]

// 类数组对象
var obj = { 0: "a", 1: "b", 2: "c"};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]

// 使用Array.forEach输出属性名和属性值
Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
  console.log(val + " -> " + obj[val]);
});
// 输出
// 0 -> a
// 1 -> b
// 2 -> c

//不可枚举属性
var my_obj = Object.create({}, {
  getFoo: {
    value: function() { return this.foo; },
    enumerable: false
  }
});
my_obj.foo = 1;

console.log(Object.getOwnPropertyNames(my_obj).sort()); // ["foo", "getFoo"]

/*如果你只要获取到可枚举属性，查看Object.keys或用for...in循环（还会获取到原型链上的可枚举属性，不过可以使用hasOwnProperty()方法过滤掉）。

下面的例子演示了该方法不会获取到原型链上的属性：
*/
function ParentClass() {}
ParentClass.prototype.inheritedMethod = function() {};

function ChildClass() {
  this.prop = 5;
  this.method = function() {};
}

ChildClass.prototype = new ParentClass;
ChildClass.prototype.prototypeMethod = function() {};

console.log(
  Object.getOwnPropertyNames(
    new ChildClass()  // ["prop", "method"]
  )
);

/*
只获取不可枚举的属性节
下面的例子使用了 Array.prototype.filter() 方法，从所有的属性名数组（使用Object.getOwnPropertyNames()方法获得）
中去除可枚举的属性（使用Object.keys()方法获得），剩余的属性便是不可枚举的属性了：
*/
var target = myObject;
var enum_and_nonenum = Object.getOwnPropertyNames(target);
var enum_only = Object.keys(target);
var nonenum_only = enum_and_nonenum.filter(function(key) {
    var indexInEnum = enum_only.indexOf(key);
    if (indexInEnum == -1) {
        // 没有发现在enum_only健集中意味着这个健是不可枚举的,
        // 因此返回true 以便让它保持在过滤结果中
        return true;
    } else {
        return false;
    }
});
console.log(nonenum_only);
//注：Array.filter(filt_func)方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素

/*
提示节
在 ES5 中，如果参数不是一个原始对象类型，将抛出一个 TypeError  异常。在 ES2015 中，非对象参数被强制转换为对象 。
*/
Object.getOwnPropertyNames('foo');
// TypeError: "foo" is not an object (ES5 code)
Object.getOwnPropertyNames('foo');
// ['length', '0', '1', '2']  (ES2015 code)

/*
Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。
语法节
Object.getOwnPropertySymbols(obj)
参数节
obj
要返回 Symbol 属性的对象。
返回值节
在给定对象自身上找到的所有 Symbol 属性的数组。
描述:
与Object.getOwnPropertyNames()类似，您可以将给定对象的所有符号属性作为 Symbol 数组获取。 
请注意，Object.getOwnPropertyNames()本身不包含对象的 Symbol 属性，只包含字符串属性。
因为所有的对象在初始化的时候不会包含任何的 Symbol，除非你在对象上赋值了 Symbol 
否则Object.getOwnPropertySymbols()只会返回一个空的数组。
*/
var obj = {};
var a = Symbol("a");
var b = Symbol.for("b");

obj[a] = "localSymbol";
obj[b] = "globalSymbol";

var objectSymbols = Object.getOwnPropertySymbols(obj);

console.log(objectSymbols.length); // 2
console.log(objectSymbols)         // [Symbol(a), Symbol(b)]
console.log(objectSymbols[0])      // Symbol(a)

/*
Object.getPrototypeOf() 方法返回指定对象的原型（内部[[Prototype]]属性的值）。
语法节
Object.getPrototypeOf(object)
参数节
obj
要返回其原型的对象。
返回值
给定对象的原型。如果没有继承属性，则返回 null 。
*/
const prototype1 = {};
const object1 = Object.create(prototype1);

console.log(Object.getPrototypeOf(object1) === prototype1);
// expected output: true

/*
Object.is() 方法判断两个值是否是相同的值。
语法节
Object.is(value1, value2);
参数节
value1
第一个需要比较的值。
value2
第二个需要比较的值。
返回值节
表示两个参数是否相同的布尔值 。
描述节:
Object.is() 判断两个值是否相同。如果下列任何一项成立，则两个值相同：
两个值都是 undefined
两个值都是 null
两个值都是 true 或者都是 false
两个值是由相同个数的字符按照相同的顺序组成的字符串
两个值指向同一个对象
两个值都是数字并且
都是正零 +0
都是负零 -0
都是 NaN
都是除零和 NaN 外的其它同一个数字
这种相等性判断逻辑和传统的 == 运算不同，== 运算符会对它两边的操作数做隐式类型转换（如果它们类型不同），
然后才进行相等性比较，（所以才会有类似 "" == false 等于 true 的现象），但 Object.is 不会做这种类型转换。
这与 === 运算符的判定方式也不一样。=== 运算符（和== 运算符）将数字值 -0 和 +0 视为相等，
并认为 Number.NaN 不等于 NaN。
*/
Object.is('foo', 'foo');     // true
Object.is(window, window);   // true

Object.is('foo', 'bar');     // false
Object.is([], []);           // false

var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo);         // true
Object.is(foo, bar);         // false

Object.is(null, null);       // true

// 特例
Object.is(0, -0);            // false
Object.is(0, +0);            // true
Object.is(-0, -0);           // true
Object.is(NaN, 0/0);         // true

if (!Object.is) {
  Object.is = function(x, y) {
    // SameValue algorithm
    if (x === y) { // Steps 1-5, 7-10
      // Steps 6.b-6.e: +0 != -0
      return x !== 0 || 1 / x === 1 / y;
    } else {
      // Step 6.a: NaN == NaN
      return x !== x && y !== y;
    }
  };
}

/*概述节
Object.isExtensible() 方法判断一个对象是否是可扩展的（是否可以在它上面添加新的属性）。

语法节
Object.isExtensible(obj)
参数节
obj
需要检测的对象
返回值节
    表示给定对象是否可扩展的一个Boolean 。

描述:
默认情况下，对象是可扩展的：即可以为他们添加新的属性。以及它们的 __proto__ 属性可以被更改。
Object.preventExtensions，Object.seal 或 Object.freeze 方法都可以标记一个对象为不可扩展（non-extensible）。
*/

var empty = {};
Object.isExtensible(empty); // === true  // 新对象默认是可扩展的.
Object.preventExtensions(empty);
Object.isExtensible(empty); // === false    // ...可以变的不可扩展.
var sealed = Object.seal({});
Object.isExtensible(sealed); // === false     // 密封对象是不可扩展的.
var frozen = Object.freeze({});
Object.isExtensible(frozen); // === false     // 冻结对象也是不可扩展.

/*在 ES5 中，如果参数不是一个对象类型，将抛出一个 TypeError 异常。在 ES6 中， 
non-object 参数将被视为一个不可扩展的普通对象，因此会返回 false 。
*/

Object.isExtensible(1);     // TypeError: 1 is not an object (ES5 code)
Object.isExtensible(1);     // false                         (ES6 code)

// 一个对象默认是可扩展的,所以它也是非冻结的.
Object.isFrozen({}); // === false

// 一个不可扩展的空对象同时也是一个冻结对象.
var vacuouslyFrozen = Object.preventExtensions({});
Object.isFrozen(vacuouslyFrozen) //=== true;

// 一个非空对象默认也是非冻结的.
var oneProp = { p: 42 };
Object.isFrozen(oneProp) //=== false

// 让这个对象变的不可扩展,并不意味着这个对象变成了冻结对象,
// 因为p属性仍然是可以配置的(而且可写的).
Object.preventExtensions(oneProp);
Object.isFrozen(oneProp) //=== false

// 此时,如果删除了这个属性,则它会成为一个冻结对象.
delete oneProp.p;
Object.isFrozen(oneProp) //=== true

// 一个不可扩展的对象,拥有一个不可写但可配置的属性,则它仍然是非冻结的.
var nonWritable = { e: "plep" };
Object.preventExtensions(nonWritable);
Object.defineProperty(nonWritable, "e", { writable: false }); // 变得不可写
Object.isFrozen(nonWritable) //=== false

// 把这个属性改为不可配置,会让这个对象成为冻结对象.
Object.defineProperty(nonWritable, "e", { configurable: false }); // 变得不可配置
Object.isFrozen(nonWritable) //=== true

// 一个不可扩展的对象,拥有一个不可配置但可写的属性,则它仍然是非冻结的.
var nonConfigurable = { release: "the kraken!" };
Object.preventExtensions(nonConfigurable);
Object.defineProperty(nonConfigurable, "release", { configurable: false });
Object.isFrozen(nonConfigurable) //=== false

// 把这个属性改为不可写,会让这个对象成为冻结对象.
Object.defineProperty(nonConfigurable, "release", { writable: false });
Object.isFrozen(nonConfigurable) //=== true

// 一个不可扩展的对象,值拥有一个访问器属性,则它仍然是非冻结的.
var accessor = { get food() { return "yum"; } };
Object.preventExtensions(accessor);
Object.isFrozen(accessor) //=== false

// ...但把这个属性改为不可配置,会让这个对象成为冻结对象.
Object.defineProperty(accessor, "food", { configurable: false });
Object.isFrozen(accessor) //=== true

// 使用Object.freeze是冻结一个对象最方便的方法.
var frozen = { 1: 81 };
Object.isFrozen(frozen) //=== false
Object.freeze(frozen);
Object.isFrozen(frozen) //=== true

// 一个冻结对象也是一个密封对象.
Object.isSealed(frozen) //=== true

// 当然,更是一个不可扩展的对象.
Object.isExtensible(frozen) //=== false
/*
注意
在 ES5 中，如果参数不是一个对象类型，将抛出一个TypeError异常。在 ES2015 中，非对象参数将被视为一个冻结的普通对象，因此会返回true。
*/
Object.isFrozen(1);
// TypeError: 1 is not an object (ES5 code)

Object.isFrozen(1);
// true                          (ES2015 code)
/*

Object.isSealed() 方法判断一个对象是否被密封。

语法节
Object.isSealed(obj)
参数节
obj
要被检查的对象。
返回值节
表示给定对象是否被密封的一个Boolean 。

描述:
如果这个对象是密封的，则返回 true，否则返回 false。密封对象是指那些不可 扩展 的，
且所有自身属性都不可配置且因此不可删除（但不一定是不可写）的对象。
*/

// 新建的对象默认不是密封的.
var empty = {};
Object.isSealed(empty); // === false

// 如果你把一个空对象变的不可扩展,则它同时也会变成个密封对象.
Object.preventExtensions(empty);
Object.isSealed(empty); // === true

// 但如果这个对象不是空对象,则它不会变成密封对象,因为密封对象的所有自身属性必须是不可配置的.
var hasProp = { fee: "fie foe fum" };
Object.preventExtensions(hasProp);
Object.isSealed(hasProp); // === false

// 如果把这个属性变的不可配置,则这个对象也就成了密封对象.
Object.defineProperty(hasProp, "fee", { configurable: false });
Object.isSealed(hasProp); // === true

// 最简单的方法来生成一个密封对象,当然是使用Object.seal.
var sealed = {};
Object.seal(sealed);
Object.isSealed(sealed); // === true

// 一个密封对象同时也是不可扩展的.
Object.isExtensible(sealed); // === false

// 一个密封对象也可以是一个冻结对象,但不是必须的.
Object.isFrozen(sealed); // === true ，所有的属性都是不可写的
var s2 = Object.seal({ p: 3 });
Object.isFrozen(s2); // === false， 属性"p"可写

var s3 = Object.seal({ get p() { return 0; } });
Object.isFrozen(s3); // === true ，访问器属性不考虑可写不可写,只考虑是否可配置

/*
注意节
在ES5中，如果这个方法的参数不是一个对象（一个原始类型），那么它会导致TypeError。在ES2015中，非对象参数将被视为是一个密封的普通对象，只返回true。
*/
Object.isSealed(1);     // TypeError: 1 is not an object (ES5 code)
Object.isSealed(1);      // true                          (ES2015 code)

/*
Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组，数组中属性名的排列顺序和
使用 for...in 循环遍历该对象时返回的顺序一致 。如果对象的键-值都不可枚举，那么将返回由键组成的数组。
语法节
Object.keys(obj)
参数节
obj: 要返回其枚举自身属性的对象。
返回值: 一个表示给定对象的所有可枚举属性的字符串数组。
描述节
Object.keys 返回一个所有元素为字符串的数组，其元素来自于从给定的object上面可直接枚举的属性。
这些属性的顺序与手动遍历该对象属性时的一致。
*/

// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']

// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

// array like object with random key ordering
var anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']

// getFoo is a property which isn't enumerable
var myObj = Object.create({}, {
  getFoo: {
    value: function () { return this.foo; }
  } 
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']


/*
在ES5里，如果此方法的参数不是对象（而是一个原始值），那么它会抛出 TypeError。在ES2015中，非对象的参数将被强制转换为一个对象。
*/
Object.keys("foo");     // TypeError: "foo" is not an object (ES5 code)

Object.keys("foo");     // ["0", "1", "2"]                   (ES2015 code)

/*Polyfill节
要在原生不支持的旧环境中添加兼容的Object.keys，请复制以下代码段：
*/

if (!Object.keys) {
  Object.keys = (function () {
    var hasOwnProperty = Object.prototype.hasOwnProperty,
        hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
        dontEnums = [
          'toString',
          'toLocaleString',
          'valueOf',
          'hasOwnProperty',
          'isPrototypeOf',
          'propertyIsEnumerable',
          'constructor'
        ],
        dontEnumsLength = dontEnums.length;

    return function (obj) {
      if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object');

      var result = [];

      for (var prop in obj) {
        if (hasOwnProperty.call(obj, prop)) result.push(prop);
      }

      if (hasDontEnumBug) {
        for (var i=0; i < dontEnumsLength; i++) {
          if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
        }
      }
      return result;
    }
  })()
};
//上面的代码在IE7（也许IE8也是）下有个问题，就是如果传入一个来自其他 window 对象下的对象时，不可枚举的属性也会被获取到
/*
Object.preventExtensions()方法让一个对象变的不可扩展，也就是永远不能再添加新的属性。
语法节
Object.preventExtensions(obj)
参数节
obj
将要变得不可扩展的对象。
返回值节
已经不可扩展的对象。
描述节
如果一个对象可以添加新的属性，则这个对象是可扩展的。Object.preventExtensions()将对象标记为不再可扩展，
因此它将永远不会具有超出它被标记为不可扩展的属性。注意，一般来说，不可扩展对象的属性可能仍然可被删除。
尝试将新属性添加到不可扩展对象将静默失败或抛出TypeError（最常见但不排除其他情况，如在strict mode中）。

Object.preventExtensions()仅阻止添加自身的属性。但属性仍然可以添加到对象原型。

一旦使其不可扩展，就无法再对象进行扩展。
*/
// Object.preventExtensions将原对象变的不可扩展,并且返回原对象.
var obj = {};
var obj2 = Object.preventExtensions(obj);
obj === obj2;  // true
 
// 字面量方式定义的对象默认是可扩展的.
var empty = {};
Object.isExtensible(empty) //=== true
 
// ...但可以改变.
Object.preventExtensions(empty);
Object.isExtensible(empty) //=== false
 
// 使用Object.defineProperty方法为一个不可扩展的对象添加新属性会抛出异常.
var nonExtensible = { removable: true };
Object.preventExtensions(nonExtensible);
Object.defineProperty(nonExtensible, "new", { value: 8675309 }); // 抛出TypeError异常
 
// 在严格模式中,为一个不可扩展对象的新属性赋值会抛出TypeError异常.
function fail()
{
  "use strict";
  nonExtensible.newProperty = "FAIL"; // throws a TypeError
}
fail();
 
// 一个不可扩展对象的原型是不可更改的,__proto__是个非标准魔法属性,可以更改一个对象的原型.
var fixed = Object.preventExtensions({});
fixed.__proto__ = { oh: "hai" }; // 抛出TypeError异常
/*Notes节
在 ES5 中，如果参数不是一个对象类型，将抛出一个TypeError异常。在 ES2015 中，
非对象参数将被视为一个不可扩展的普通对象，因此会被直接返回。
*/
Object.preventExtensions(1);
// TypeError: 1 is not an object (ES5 code)

Object.preventExtensions(1);
// 1                             (ES2015 code)

//Object.seal()方法封闭一个对象，阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。
 
const object1 = {
  property1: 42
};

Object.seal(object1);
object1.property1 = 33;
console.log(object1.property1);
// expected output: 33

delete object1.property1; // cannot delete when sealed
console.log(object1.property1);
// expected output: 33
//--------------------------------------------------------------------------------------------------
/*
Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性）到另一个对象或  null。
 
警告: 由于现代 JavaScript 引擎优化属性访问所带来的特性的关系，更改对象的 [[Prototype]]在各个浏览器和 
JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的，
这不仅仅限于 obj.__proto__ = ... 语句上的时间花费，而且可能会延伸到任何代码，那些可以访问任何
[[Prototype]]已被更改的对象的代码。如果你关心性能，你应该避免设置一个对象的 [[Prototype]]。相反，
你应该使用 Object.create()来创建带有你想要的[[Prototype]]的新对象。

语法
Object.setPrototypeOf(obj, prototype)
参数
obj
要设置其原型的对象。.
prototype
该对象的新原型(一个对象 或 null).
描述节
如果对象的[[Prototype]]被修改成不可扩展(通过 Object.isExtensible()查看)，就会抛出 TypeError异常。
如果prototype参数不是一个对象或者null(例如，数字，字符串，boolean，或者 undefined)，则什么都不做。
否则，该方法将obj的[[Prototype]]修改为新的值。
Object.setPrototypeOf()是ECMAScript 6最新草案中的方法，相对于 Object.prototype.__proto__ ，
它被认为是修改对象原型更合适的方法
*/
var dict = Object.setPrototypeOf({}, null);

/*Polyfill
我们必须借助非标准的  
使用较旧的 Object.prototype.__proto__ 属性，我们可以很容易地定义Object.setPrototypeOf 如果它不可用：.
*/
// 仅适用于Chrome和FireFox，在IE中不工作：
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
  obj.__proto__ = proto;
  return obj; 
}


/*附加原型链节
通过  Object.getPrototypeOf() 和 Object.prototype.__proto__ 的组合允许将一个整个原型链附加到一个新的原型对象：
*/
/**
*** Object.appendChain(@object, @prototype)
*
* Appends the first non-native prototype of a chain to a new prototype.
* Returns @object (if it was a primitive value it will transformed into an object).
*
*** Object.appendChain(@object [, "@arg_name_1", "@arg_name_2", "@arg_name_3", "..."], "@function_body")
*** Object.appendChain(@object [, "@arg_name_1, @arg_name_2, @arg_name_3, ..."], "@function_body")
*
* Appends the first non-native prototype of a chain to the native Function.prototype object, then appends a
* new Function(["@arg"(s)], "@function_body") to that chain.
* Returns the function.
*
**/

Object.appendChain = function(oChain, oProto) {
  if (arguments.length < 2) { 
    throw new TypeError('Object.appendChain - Not enough arguments');
  }
  if (typeof oProto === 'number' || typeof oProto === 'boolean') {
    throw new TypeError('second argument to Object.appendChain must be an object or a string');
  }

  var oNewProto = oProto,
      oReturn, 
      o2nd, 
      oLast;
      
  oReturn = o2nd = oLast = oChain instanceof this ? oChain : new oChain.constructor(oChain);

  for (var o1st = this.getPrototypeOf(o2nd);
    o1st !== Object.prototype && o1st !== Function.prototype;
    o1st = this.getPrototypeOf(o2nd)
  ) {
    o2nd = o1st;
  }

  if (oProto.constructor === String) {
    oNewProto = Function.prototype;
    oReturn = Function.apply(null, Array.prototype.slice.call(arguments, 1));
    this.setPrototypeOf(oReturn, oLast);
  }

  this.setPrototypeOf(o2nd, oNewProto);
  return oReturn;
}
/*使用节
例子一：向一个原型附加一个链
*/

function Mammal() {
  this.isMammal = 'yes';
}

function MammalSpecies(sMammalSpecies) {
  this.species = sMammalSpecies;
}

MammalSpecies.prototype = new Mammal();
MammalSpecies.prototype.constructor = MammalSpecies;

var oCat = new MammalSpecies('Felis');

console.log(oCat.isMammal); 
// 'yes'

function Animal() {
  this.breathing = 'yes';
}

Object.appendChain(oCat, new Animal());

console.log(oCat.breathing); 
// 'yes'
//例子二：将一个基本类型转化为对应的对象类型并添加到原型链上

function Symbol() {
  this.isSymbol = 'yes';
}

var nPrime = 17;

console.log(typeof nPrime); // 'number'

var oPrime = Object.appendChain(nPrime, new Symbol());

console.log(oPrime); // '17'
console.log(oPrime.isSymbol); // 'yes'
console.log(typeof oPrime); // 'object'
//例子三：给函数类型的对象添加一个链，并添加一个新的方法到那个链上

function Person(sName) {
  this.identity = sName;
}

var george = Object.appendChain(new Person('George'), 'console.log("Hello guys!!");');

console.log(george.identity); // 'George'
george(); // 'Hello guys!!'
//---------------------------------------------------------------------------------------------------
/*
Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组，值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

语法
Object.values(obj)
参数
obj
被返回可枚举属性值的对象。
返回值
一个包含对象自身的所有可枚举属性值的数组。
描述
Object.values()返回一个数组，其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同。
*/

var obj = { foo: 'bar', baz: 42 };
console.log(Object.values(obj)); // ['bar', 42]

// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.values(obj)); // ['a', 'b', 'c']

// array like object with random key ordering
// when we use numeric keys, the value returned in a numerical order according to the keys
var an_obj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.values(an_obj)); // ['b', 'c', 'a']

// getFoo is property which isn't enumerable
var my_obj = Object.create({}, { getFoo: { value: function() { return this.foo; } } });
my_obj.foo = 'bar';
console.log(Object.values(my_obj)); // ['bar']

// non-object argument will be coerced to an object
console.log(Object.values('foo')); // ['f', 'o', 'o']
/*如果要 Object.values兼容不支持它的旧环境，可在 tc39/proposal-object-values-entries 或 es-shims/Object.values 中找到 Polyfill 。

根据Object.keys()的Polyfill仿写一个：
*/

if (!Object.values) Object.values = function(obj) {
    if (obj !== Object(obj))
        throw new TypeError('Object.values called on a non-object');
    var val=[],key;
    for (key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj,key)) {
            val.push(obj[key]);
        }
    }
    return val;
}

//---------------------------------------------------------------------------------------------------------------------
/*
  正则表达式
    正则表达式(regular expression)描述了一种字符串匹配的模式（pattern），
    可以用来检查一个串是否含有某种子串、
    将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
*/
^[0-9]+abc$
 /*
  ^ 为匹配输入字符串的开始位置。
  [0-9]+匹配多个数字， [0-9] 匹配单个数字，+ 匹配一个或者多个。
  abc$匹配字母 abc 并以 abc 结尾，$ 为匹配输入字符串的结束位置。
  */
var str = "abc123def";
var patt1 = /[0-9]+/;
document.write(str.match(patt1));   //返回123
var emil =/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/  // 邮箱的正则表达式
  /* 
    名称允许汉字、字母、数字，域名只允许英文域名
    汉字在正则表示为[\u4e00-\u9fa5]
    字母和数字表示为A-Za-z0-9 
  */ 
 var emil2 = /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/   // 通过分析得出邮件名称部分表达式为
  /*
    这个表达式的意思是：
    1--以1为开头；
    2--第二位可为3,4,5,7,8,中的任意一位；
    3--最后以0-9的9个整数结尾。
  */
 var myreg=/^[1][3,4,5,7,8][0-9]{9}$/;
//---------------------------------------------------------------------------------------------------------------------
/*
math对象方法
math.abs(x) 返回x的绝对值
math.ceil(x) 返回大于等于数字参数的最小整数(取整函数)，x的上舍入   
math.floor() 返回小于等于数字参数的最大整数，对数字进行下舍入
值 = Math.floor(Math.random() * 可能值的总数 + 第一个可能的值)
math.max()    返回最大值
math.min()    返回最小值
math. random()    返回大于等于 0 小于 1 的一个随机数,利用它来随机显示一些名人名言和新闻事件
math.pI   返回TT
math.sqrt(X)  返回X的平方根
math.sin（x）    返回的是x的正弦（对边比斜边）
math.cos（x）    返回的是x的余弦（邻边比斜边）
math.tan(x)      返回的是x的正切（对边比邻边)
math.asin(x)     返回的是x的反正弦值
math.acos（x）    返回的是x的反余弦值
math.atan（x）    返回的是x的反正切值
*/
var pi_value=Math.PI;
var i = Math.random("")  //返回绝对值不考虑正负
// var sqrt_value=Math.sqrt(15);
// document.write(Math.ceil(-5.99));  //-5
// document.write(Math.ceil(5.99));   //6
console.log(i)

rd = Math.floor(Math.random()*2+3)
function selectFrom(startNumber,endNumber){
  var choice = endNumber-startNumber+1;
  return Math.floor(Math.random()*choice+startNumber)
}
var rand2 = selectFrom(1,9)
console.log(rand2)   //随机产生1-9之间的数字
var rand3 = Math.random() < 0.5 ? 2 : 4;  //随机产生2或4中的一个数
var rand4 = selectFromMess(1, 6, 8);      //随机产生1、6、8中的一个数
var randomTxt1 = selectFromMess("安慰奖", "二等奖", "一等奖");   //也可随机产生文本
// 每次要输入这么多参数比较麻烦，可以改写一下函数
function selectFromMessArray(arr) {
    return arr[Math.floor(Math.random() * arr.length)]
}
var arrayTxt=["一","二","三","四","五"];
var randTxt2 = selectFromMessArray(arrayTxt);
var randTxt3 = selectFromMess.apply(null,arrayTxt); // 或者不改变原有方法，可以利用apply()这个方法传递数组参数
//---------------------------------------------------------------------------------------------------------------------------------
/*
 JavaScript
闭包 闭包是一个函数与其本身所被定义的词法环境的结合。 高级内容 继承和原型链 基于原型继承被外界广泛地误解与低估，这一版块对基于原型的继承作出详细
解释。…如何使用比较操作符 JavaScript 提供了三种比较操作符，包括严格比较操作符 === 和非严格的比较操作符 ==，以及 Object.is() 方法。…Firefox 
开发工具 包括 Scratchpad、Web Console、JavaScript Profiler、Debugger 等等 JavaScript Shells 允许您快速测试 JavaScript
*/

function init() {
  var name = "Mozilla"; // name 是一个被 init 创建的局部变量
  function displayName() { // displayName() 是内部函数,一个闭包
      alert(name); // 使用了父函数中声明的变量
  }
  displayName();
}
init();
/*
init() 创建了一个局部变量 name 和一个名为 displayName() 的函数。displayName() 是定义在 init() 里的内部函数，仅在该函数体内可用。
displayName() 内没有自己的局部变量，然而它可以访问到外部函数的变量，所以 displayName() 可以使用父函数 init() 中声明的变量 name 。
但是，如果有同名变量 name 在 displayName() 中被定义，则会使用 displayName() 中定义的 name 。
*/
function init() {
  var name = "Mozilla"; // name is a local variable created by init
  function displayName() { // displayName() is the inner function, a closure
      alert (name); // displayName() uses variable declared in the parent function    
  }
 displayName();    
}
init();
/*
运行代码可以发现 displayName() 内的 alert() 语句成功的显示了在其父函数中声明的 name 变量的值。这个词法作用域的例子介绍了引擎是如何
解析函数嵌套中的变量的。词法作用域中使用的域，是变量在代码中声明的位置所决定的。嵌套的函数可以访问在其外部声明的变量。
*/

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
      alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();
/*
JavaScript中的函数会形成闭包。 闭包是由函数以及创建该函数的词法环境组合而成。这个环境包含了这个闭包创建时所能访问的所有局部变量。在我们的例子中，
myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用，而 displayName 实例仍可访问其词法作用域中的变量，即可以访问到 name 。
由此，当 myFunc 被调用时，name 仍可被访问，其值 Mozilla 就被传递到alert中
*/

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12
/*
在这个示例中，我们定义了 makeAdder(x) 函数，它接受一个参数 x ，并返回一个新的函数。返回的函数接受一个参数 y，并返回x+y的值。
从本质上讲，makeAdder 是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中，我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和，另一个和 10 求和。
add5 和 add10 都是闭包。它们共享相同的函数定义，但是保存了不同的词法环境。在 add5 的环境中，x 为 5。而在 add10 中，x 则为 10。
*/